module user_ip (
  // Z80 bus interface.
  inout              sram_cs_n,                       // SRAM chip select.
  inout       [15:0] z80_addr,                        // Z80 address bus.
  inout       [7:0]  z80_data,                        // Z80 data bus.
  inout              z80_rd_n,                        // Z80 read enable.
  inout              z80_wr_n,                        // Z80 write enable.

  // Clock and reset.
  input              sys_clock,                       // System clock for AHB.
  input              bus_clock,                       // APB peripheral clock.
  input              resetn,                          // Asynchronous reset.
  input              stop,

  input       [1:0]  mem_ahb_htrans,
  input              mem_ahb_hready,
  input              mem_ahb_hwrite,
  input       [31:0] mem_ahb_haddr,
  input       [2:0]  mem_ahb_hsize,
  input       [2:0]  mem_ahb_hburst,
  input       [31:0] mem_ahb_hwdata,
  output tri1        mem_ahb_hreadyout,
  output tri0        mem_ahb_hresp,
  output tri0 [31:0] mem_ahb_hrdata,
  output tri0        slave_ahb_hsel,
  output tri1        slave_ahb_hready,
  input              slave_ahb_hreadyout,
  output tri0 [1:0]  slave_ahb_htrans,
  output tri0 [2:0]  slave_ahb_hsize,
  output tri0 [2:0]  slave_ahb_hburst,
  output tri0        slave_ahb_hwrite,
  output tri0 [31:0] slave_ahb_haddr,
  output tri0 [31:0] slave_ahb_hwdata,
  input              slave_ahb_hresp,
  input       [31:0] slave_ahb_hrdata,
  output tri0 [3:0]  ext_dma_DMACBREQ,
  output tri0 [3:0]  ext_dma_DMACLBREQ,
  output tri0 [3:0]  ext_dma_DMACSREQ,
  output tri0 [3:0]  ext_dma_DMACLSREQ,
  input       [3:0]  ext_dma_DMACCLR,
  input       [3:0]  ext_dma_DMACTC,
  output tri0 [3:0]  local_int
);

//----------------------------------------------------------------------------
// APB interface signals.
//----------------------------------------------------------------------------
wire                 apb_psel;                        // APB peripheral select.
wire                 apb_penable;                     // APB enable (ACCESS phase).
wire                 apb_pwrite;                      // APB write enable.
wire [31:0]          apb_paddr;                       // APB address bus.
wire [31:0]          apb_pwdata;                      // APB write data.
wire [3:0]           apb_pstrb;                       // APB byte strobe.
wire [2:0]           apb_pprot;                       // APB protection control.
wire                 apb_pready;                      // APB ready from slave.
wire                 apb_pslverr;                     // APB slave error.
wire [31:0]          apb_prdata;                      // APB read data.

// AHB-to-APB bridge instance.
ahb2apb ahb2apb_inst (
  .reset        (!resetn                     ),
  .ahb_clock    (sys_clock                   ),
  .ahb_hmastlock(1'b0                        ),
  .ahb_htrans   (mem_ahb_htrans              ),
  .ahb_hsel     (1'b1                        ),
  .ahb_hready   (mem_ahb_hready              ),
  .ahb_hwrite   (mem_ahb_hwrite              ),
  .ahb_haddr    (mem_ahb_haddr               ),
  .ahb_hsize    (mem_ahb_hsize               ),
  .ahb_hburst   (mem_ahb_hburst              ),
  .ahb_hprot    (4'b0011                     ),
  .ahb_hwdata   (mem_ahb_hwdata              ),
  .ahb_hrdata   (mem_ahb_hrdata              ),
  .ahb_hreadyout(mem_ahb_hreadyout           ),
  .ahb_hresp    (mem_ahb_hresp               ),
  .apb_clock    (bus_clock                   ),
  .apb_psel     (apb_psel                    ),
  .apb_penable  (apb_penable                 ),
  .apb_pwrite   (apb_pwrite                  ),
  .apb_paddr    (apb_paddr                   ),
  .apb_pwdata   (apb_pwdata                  ),
  .apb_pstrb    (apb_pstrb                   ),
  .apb_pprot    (apb_pprot                   ),
  .apb_pready   (apb_pready                  ),
  .apb_pslverr  (apb_pslverr                 ),
  .apb_prdata   (apb_prdata                  )
);

//----------------------------------------------------------------------------
// Peripheral addresses.
//----------------------------------------------------------------------------
localparam REG0_ADDR          = 24'h000000;           // Reg0
localparam REG1_ADDR          = 24'h000004;           // Reg1
localparam REG2_ADDR          = 24'h000008;           // Reg2
localparam REG3_ADDR          = 24'h00000C;           // Reg3
localparam REG4_ADDR          = 24'h000010;           // Reg4
localparam REG5_ADDR          = 24'h000014;           // Reg5
localparam REG6_ADDR          = 24'h000018;           // Reg6
localparam REG7_ADDR          = 24'h00001C;           // Reg7
localparam REG_SRAM_ADDR_ADDR = 24'h000020;           // SRAM address register
localparam REG_SRAM_DATA_ADDR = 24'h000024;           // SRAM data register

//----------------------------------------------------------------------------
// APB register interface.
//----------------------------------------------------------------------------
wire [31:0] paddr;
wire [31:0] pwdata;
reg  [31:0] prdata;
reg         pready;
reg         pslverr;

assign      pclk        = bus_clock;
assign      presetn     = resetn;
assign      psel        = apb_psel;
assign      penable     = apb_penable;
assign      pwrite      = apb_pwrite;
assign      paddr       = apb_paddr;
assign      pwdata      = apb_pwdata;
assign      apb_pslverr = pslverr;
assign      apb_prdata  = prdata;
assign      apb_pready  = pready;

//----------------------------------------------------------------------------
// Data register: REG0
//----------------------------------------------------------------------------
reg [31:0] reg0_out;
reg        reg0_pready;

apb_reg #(.REG_ADDR(REG0_ADDR)) u_reg0 (
  .pclk          (pclk                        ),
  .presetn       (resetn                      ),
  .psel          (psel                        ),
  .penable       (penable                     ),
  .pwrite        (pwrite                      ),
  .paddr         (paddr                       ),
  .pwdata        (pwdata                      ),
  .prdata        (reg0_out                    ),
  .pready        (reg0_pready                 ),
  .ext_inc_en    (1'b0                        ),
  .ext_inc_pulse (1'b0                        )
);

//----------------------------------------------------------------------------
// Data register: REG1
//----------------------------------------------------------------------------
reg [31:0] reg1_out;
reg        reg1_pready;

apb_reg #(.REG_ADDR(REG1_ADDR)) u_reg1 (
  .pclk          (pclk                        ),
  .presetn       (resetn                      ),
  .psel          (psel                        ),
  .penable       (penable                     ),
  .pwrite        (pwrite                      ),
  .paddr         (paddr                       ),
  .pwdata        (pwdata                      ),
  .prdata        (reg1_out                    ),
  .pready        (reg1_pready                 ),
  .ext_inc_en    (1'b0                        ),      // External auto-increment disabled.
  .ext_inc_pulse (1'b0                        )
);

//----------------------------------------------------------------------------
// Data register: REG2
//----------------------------------------------------------------------------
reg [31:0] reg2_out;
reg        reg2_pready;

apb_reg #(.REG_ADDR(REG2_ADDR)) u_reg2 (
  .pclk          (pclk                        ),
  .presetn       (resetn                      ),
  .psel          (psel                        ),
  .penable       (penable                     ),
  .pwrite        (pwrite                      ),
  .paddr         (paddr                       ),
  .pwdata        (pwdata                      ),
  .prdata        (reg2_out                    ),
  .pready        (reg2_pready                 ),
  .ext_inc_en    (1'b0                        ),
  .ext_inc_pulse (1'b0                        )
);

//----------------------------------------------------------------------------
// Data register: REG_SRAM_ADDR (auto-increment)
//----------------------------------------------------------------------------
reg [31:0] reg_sram_addr;
reg        reg_sram_addr_pready;
wire       sram_access_done_pulse;

apb_reg #(.REG_ADDR(REG_SRAM_ADDR_ADDR)) u_reg_sram_addr (
  .pclk          (pclk                        ),
  .presetn       (resetn                      ),
  .psel          (psel                        ),
  .penable       (penable                     ),
  .pwrite        (pwrite                      ),
  .paddr         (paddr                       ),
  .pwdata        (pwdata                      ),
  .prdata        (reg_sram_addr               ),
  .pready        (reg_sram_addr_pready        ),
  .ext_inc_en    (1'b1                        ),
  .ext_inc_pulse (sram_access_done_pulse      )
);

//----------------------------------------------------------------------------
// SRAM Device: u_sram
//----------------------------------------------------------------------------
localparam ADDR_BITS = 16;
localparam DATA_BITS = 8;

// Driving the SRAM bus.
reg [ADDR_BITS-1:0] sram_addr;                        // SRAM address bus.
reg [31:0]          sram_data;                        // Data read from SRAM.
reg                 sram_data_pready;                 // APB ready signal for SRAM access.
reg                 sram_ce_n;                        // SRAM chip enable.
reg                 sram_we_n;                        // SRAM write enable.
reg                 sram_oe_n;                        // SRAM read enable.

// APB SRAM instance.
// WAIT_CYCLE=5 ensures that the sram ACCESS-TIME is greater than 100ns
apb_sram #(REG_SRAM_DATA_ADDR, ADDR_BITS, DATA_BITS, 5) u_sram (
  .pclk               (pclk                        ),
  .presetn            (resetn                      ),
  .psel               (psel                        ),
  .penable            (penable                     ),
  .pwrite             (pwrite                      ),
  .paddr              (paddr                       ),
  .pwdata             (pwdata                      ),
  .prdata             (sram_data                   ),
  .pready             (sram_data_pready            ),
  .access_addr        (reg_sram_addr               ),
  .access_done_pulse  (sram_access_done_pulse      ),
  .sram_addr          (sram_addr                   ), 
  .sram_dq            (z80_data                    ),
  .sram_cs_n          (sram_ce_n                   ),
  .sram_we_n          (sram_we_n                   ),
  .sram_oe_n          (sram_oe_n                   )
);

// Connect internal SRAM signals to Z80 bus.
assign z80_addr    = sram_addr;
assign sram_cs_n   = sram_ce_n;
assign z80_wr_n    = sram_we_n;
assign z80_rd_n    = sram_oe_n;


//----------------------------------------------------------------------------
// APB read data multiplexer
//----------------------------------------------------------------------------
always @(*) begin
  prdata  = 32'hDEAD_BEEF;
  pready  = 1'b1;
  pslverr = 1'b0;        
  
  if (psel) begin
    case (paddr[23:0])
      // Read REG0
      REG0_ADDR: begin
        prdata  = reg0_out;
        pready  = reg0_pready;
        pslverr = 1'b0;
      end
      // Read REG1
      REG1_ADDR: begin
        prdata  = reg1_out;
        pready  = reg1_pready;
        pslverr = 1'b0;
      end
      // Read REG2
      REG2_ADDR: begin
        prdata  = reg2_out;
        pready  = reg2_pready;
        pslverr = 1'b0;
      end
      // Read REG_SRAM_ADDR
      REG_SRAM_ADDR_ADDR: begin
        prdata  = reg_sram_addr;
        pready  = reg_sram_addr_pready;
        pslverr = 1'b0;
      end
      // Read SRAM data
      REG_SRAM_DATA_ADDR: begin
        prdata  = sram_data;
        pready  = sram_data_pready;
        pslverr = 1'b0;
      end
      // Unknown address
      default: begin
        prdata  = 32'hDEAD_BEEF;
        pready  = 1'b1;
        pslverr = 1'b1;
      end
    endcase
  end
end

endmodule
